// #include"Log.hpp"
// #include<iostream>
// #include<string>
// #include<cstring>
// #include<cstrings>
// #include<cstdlib>
// #include<cerrno>
// #include<sys/socket.h>
// using std::cin;
// using std::cout;
// using std::cerr;
// using std::endl;

// static void Usage(const std::string proc)
// {
//     cout << "Usage: \n\t" << proc << " proc [ip]" << endl;
// }

// class UdpServer
// {
// public:
//     UdpServer(int port, std::string ip = "")
//         :port_((uint16_t)port)
//         ,ip_(ip)
//         ,sockfd_(-1)
//     {}
//     ~UdpServer()
//     {}

// public:
//     void init()
//     {
//         // 1.创建socket套接字
//         sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
//         if(sockfd_ < 0)
//         {
//             logMessage(FATAL, "socket: %s : %d", strerror(errno), sockfd_);
//             exit(1);
//         }
//         logMessage(DEBUG, "socket create success: %d", sockfd_);

//         // 2.绑定网络信息, 指明ip + port
//         // 2.1 先填充基本信息到 struct sockaddr_in
//         struct sockaddr_in local;
//         bzero(&local, sizeof(local));//等同于memset()初始化



//     }

// private:
//     uint16_t port_; // 服务器必须有端口号信息
//     std::string ip_; // 服务器必须得有IP地址
//     int sockfd_;//服务器的socket fd信息
// };


// // ./UdpServer port [ip]
// int main(int argc, char* argv[])
// {
//     if(argc != 2 && argc != 3)
//     {
//         Usage(argv[0]);
//         exit(3);
//     }

//     uint16_t port = atoi(argv[1]);
//     std::string ip;
//     if(argc == 3)
//         ip = argv[2];
//     UdpServer svr(port, ip);
//     svr.init();
//     //svr.start();

//     return 0;
// }


// #include<iostream>
// #include<cstdio>
// #include<cstring>
// #include<string>
// #include<unordered_map>
// #include<cerrno>
// #include<cstdlib>
// #include<sys/socket.h>
// #include<sys/types.h>
// #include<netinet/in.h>
// #include<arpa/inet.h>

// #include"Log.hpp"
// using namespace std;

// static void Usage(const string proc)
// {
//     cout << "Usage:\n\t" << proc << " port [ip]" << endl;
// }

// //实现一个简单的udpServer
// //云服务器有一些特殊情况:
// //禁止你bind云服务器上的任何确定IP, 只能使用INADDR_ANY, 如果是虚拟机, 随意

// class UdpServer
// {
// public:
//     UdpServer(int port, string ip = " ")
//         :port_((uint16_t)port)
//         ,ip_(ip)
//         ,sockfd_(-1)
//     {}

//     ~UdpServer()
//     {}

// public:
//     void init()
//     {
//         //1.创建socket套接字
//         sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);//就是打开一个文件
//         if(sockfd_ < 0)
//         {
//             logMessage(FATAL, "socket:%s:%d", strerror(errno), sockfd_);
//             exit(1);
//         }
//         logMessage(DEBUG, "socket create success: %d", sockfd_);

//         //2.绑定网络信息,指明IP+PORT
//         //2.1 先填充基本信息到 struct sockaddr_in
//         struct sockaddr_in local;//local在哪里开辟空间? 用户栈 -- 临时变量 -- 写入内核中
//         bzero(&local, sizeof local);//memset()

//         //填充协议家族 -- 域
//         local.sin_family = AF_INET;
//         //填充服务器对应的端口号信息,一定是会发送给对方的,port_一定会到网络中
//         local.sin_port = htons(port_);
//         //服务器都必须具有IP地址,"xx.yy.zz.aaa",字符串风格点分十进制 -- 4字节IP -- uint32_t ip
//         //INADDR_ANY(0): 程序员不关心会bind到那一个IP,任一地址bind, 所有服务器一般的做法
//         //inet_addr: 指定填充确定的IP,特殊用途,或者测试时使用,除了做转化,还会自动给我们进行h->n
//         local.sin_addr.s_addr = ip_.empty() ? htonl(INADDR_ANY) : inet_addr(ip_.c_str());

//         //2.2绑定网络信息
//         if(bind(sockfd_, (const struct sockaddr*)&local, sizeof local) == -1)
//         {
//             logMessage(FATAL, "bind: %s:%d", strerror(errno), sockfd_);
//             exit(2);
//         }
//         logMessage(DEBUG, "socket bind success: %d", sockfd_);

//         //done
//     }

//     void start()
//     {
//         //服务器设计的时候,服务器都是死循环
//         char inbuffer[1024];//将来读取到的数据,都放在这里
//         char outbuffer[1024];//将来发送的数据,都放在这里
//         while(true)
//         {
//             struct sockaddr_in peer;//输出型参数
//             socklen_t len = sizeof peer;//输入输出型参数

//             //UDP是无连接的
//             //对方给你发了消息,你想不想给对方回消息?要的,后面两个参数是输出型参数
//             ssize_t s = recvfrom(sockfd_, inbuffer, sizeof inbuffer - 1, 0, (struct sockaddr*)&peer, &len);
//             if(s > 0)
//             {
//                 inbuffer[s] = '\0';
//             }
//             else if(s == -1)
//             {
//                 logMessage(WARNNING, "recvfrom: %s:%d", strerror(errno), sockfd_);
//                 continue;
//             }
//             //读取成功的,除了读取到对方的数据,你还要读取到对方的网络信息[IP:PORT]
//             string peerIP = inet_ntoa(peer.sin_addr);//拿到了对方的IP
//             uint32_t peerPort = ntohs(peer.sin_port);//拿到了对方的port
            
//             checkOnlineUser(peerIp, peerPort, peer);//如果存在,什么都不做,如果不存在,就添加
            
//             logMessage(NOTICE, "[%s:%d]# %s", peerIp.c_str(), peerPort, inbuffer);

//             messageRoute(peerIp, peerPort, inbuffer);//消息由路

//         }

//         void checkOnlineUser(string& ip, uint32_t port, struct sockaddr_in& peer)
//         {
//             string key = ip;
//             key += ":";
//             key += to_string(port);
//             auto iter = users.find(key);
//             if(iter == users.end())
//                 users.insert({key, peer});
//             else
//                 //do nothing
//         }

//         void messageRoute(string ip, uint32_t port, string info)
//         {
//             string message = "[";
//             message += ip;
//             message += ":";
//             message += to_string(port);
//             message += "]#";
//             message += info;

//             for(auto& user : users)
//             {
//                 sendto(sockfd_,message.c_str(),message.size(),0,(struct sockaddr*)&(user.second),sizeof user.second);
//             }
//         }

//     }


// private:
//     //服务器必须有端口号信息
//     uint16_t port_;
//     //服务器必须得有IP地址
//     string ip_;
//     //服务器的socket fd信息
//     int sockfd_;
//     //onlineuser
//     unordered_map<string, struct sockaddr_in> users;
// };

// // ./udpServer port [ip]
// int main(int argc, char* argv[])
// {
//     if(argc != 2 && argc != 3)
//     {
//         Usage(argv[0]);
//         exit(3);
//     }
//     uint16_t port = atoi(argv[1]);
//     string ip;
//     if(argc == 3)
//     {
//         ip = argv[2];
//     }

//     UdpServer svr(port, ip);
//     svr.init();
//     svr.start();
    
//     return 0;
// }


#include"Log.hpp"
#include<strings.h>
#include<iostream>
#include<string>
#include<unordered_map>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<cerrno>
using namespace std;

static void Usage(const string proc)
{ 
    cout << "Usage: \n\t" << proc << " port [ip]" << endl;
}

class udpServer
{
public:
    udpServer(int port, string ip = " ")
        :port_(port)
        ,ip_(ip)
        ,sockfd_(-1)
    {}

    ~udpServer()
    {}

public:
    //初始化操作
    void init()
    {
        //1.创建socket套接字
        sockfd_ = socket(AF_INET, SOCK_DGRAM/*面向数据报*/, 0);//就是打开一个文件
        if(sockfd_ < 0)
        {
            logMessage(FATAL, "socket: %s : %d", strerror(errno), sockfd_);
            exit(1);
        }
        logMessage(DEBUG, "socket create success: %d", sockfd_);

        //2.绑定网络信息,指明ip+port
        //2.1先填充基本信息到sockaddr_in
        struct sockaddr_in local;
        bzero(&local, sizeof local);//memset()

        //填充协议家族,域
        local.sin_family = AF_INET;//PF_INET一样--网络通信
        
        //填充服务器对应的端口号信息,一定是会发送给对方的,port_一定会到网络中
        local.sin_port = htons(port_);

        //服务器都必须具有IP地址, "xx.yy.zz.aaa" 字符串风格点分十进制 - 4字节IP - uint32_t ip
        //INADDR_ANY(0):程序员不关心会bind到哪一个IP,所以服务器一般的做法是任一地址bind
        local.sin_addr.s_addr = ip_.empty() ? htonl(INADDR_ANY) : inet_addr(ip_.c_str());

        //2.2 bind网络信息
        if(bind(sockfd_, (const struct sockaddr*)&local, sizeof(local)) == -1)
        {
            logMessage(FATAL, "bind: %s : %d", strerror(errno), sockfd_);
            exit(2);
        }
        logMessage(DEBUG, "socket bind success: %d", sockfd_);
    }

    void start()
    {
        //服务器设计的时候,都是死循环
        char inbuffer[1024];//将来读取到的数据,都放到inbuffer
        char outbuffer[1024];//将来发送的数据,都放到outbuffer

        while(true)
        {
            struct sockaddr_in peer;//输出型参数 -- 保存客户端信息
            socklen_t len = sizeof(peer);//输入输出型参数

            //udp是无连接的
            //对方给你发送了消息,你想不想给对方回消息呢?
            ssize_t s = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&peer, &len);
            if(s > 0)
            {
                inbuffer[s] = '\0';
            }
            else if(s == -1)
            {
                logMessage(WARNING, "recvfrom: %s : %d", strerror(errno), sockfd_);
                continue;
            }

            //除了读取到对方的数据,你还要读取到对方的网络地址[ip:port] -- 知道谁给你发的
            string peerIp = inet_ntoa(peer.sin_addr);//拿到对方ip -- 转字符串风格
            uint32_t peerPort = ntohs(peer.sin_port);//拿到对方port

            checkOnlineUser(peerIp, peerPort, peer);//如果存在,什么都不做;不存在,则添加.

            logMessage(NOTICE, "[%s : %d] # %s", peerIp.c_str(), peerPort, inbuffer);

            messageRoutine(peerIp, peerPort, inbuffer);//消息路由
        }
    }

    void messageRoutine(string& ip, uint32_t port, string info)
    {
        string message = "[";
        message += ip;
        message += ":";
        message += to_string(port);
        message += "]#";
        message += info;

        for(auto& user : users)
        {
            sendto(sockfd_, message.c_str(), message.size(), 0, \ 
                (const sockaddr*)&(user.second), sizeof(user.second));
        }
    }

    void checkOnlineUser(string& ip, uint32_t port, struct sockaddr_in& peer)
    {
        string key = ip;
        key += ":";
        key += to_string(port);

        auto iter = users.find(key);
        if(iter == users.end())
            users.insert({key, peer});
        else
            //do nothing
            ;
    }


private:
    uint16_t port_;//服务器必须有端口号信息
    string ip_;//服务器必须得有IP地址
    int sockfd_;//服务器的socket fd信息
    unordered_map<string, struct sockaddr_in> users;//onlineuser
};


// ./udpServer port [ip]
int main(int argc, char* argv[])
{
    if(argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(3);
    }

    uint16_t port = atoi(argv[1]);
    string ip;
    if(argc == 3)
    {
        ip = argv[2];
    }
    udpServer svr(port, ip);
    svr.init();
    svr.start();

    return 0;
}